学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

Stanford CS193p - [13-15]

Developing Applications for iOS using SwiftUI

Total Lecture: 15

current: [13-15]

Presenting Views, Navigation

.sheet and .popover

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct PaletteChooser: View {
@EnvironmentObject var store: PaletteStore
@State private var showPaletteEditor = false

var body: some View {
HStack {
chooser
view(for: store.palettes[store.cursorIndex])
}
.clipped()
.sheet(isPresented: $showPaletteEditor){
PaletteEditor(palette: $store.palettes[store.cursorIndex])
.font(nil)
}
}
}

都是模态演示,要求用户立即处理事情,不要做其他任何事情

popover放在单个View时,会显示箭头

@Binding,TextField,Form List Section

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct PaletEditor: View {
@Binding var palette: Palette

private let emojiFont = Font.system(size: 40)

var body: some View {
Form {
Section(header: Text("Name")) {
TextField("Name", text: $palete.name)
}

Section(header: Text("Emojis")) {
Text("Add Emojis Here")
.font(emojiFont)
removeEmojis
}
}
}
}

为什么字体看起来很大?
因为我们把这个东西的字体设置的很大,当我们呈现Form时,它会将字体传递给它,那么当我们转到另一张表或其他UI时,如何停止字体?

.font(nil)

@FocusState

1
2
3
4
5
enum Focused {
case name
case addEmojis
}
@FocusState private var focused: Focused?

用于跟踪哪个TextField具有键盘焦点的东西

1
2
3
4
5
6
7
8
9
10
Form{
Section(header:Text("Name")) {
TextField("Name",text: $palette.name)
.focused($focused,equals: .name)
}
Section(header: Text("Emojis")) {
TextField("Add Emojis Here",text : $emojisToAdd)
.focused($focused, equals: .addEmojis)
}
}

通过使用修饰符,告诉系统,当这个东西获得焦点时要将它设置成什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct PaletteList: View {

var body: some View {
NavigationStack {
List(store.palettes) { palette in
NavigationLink(value: palette) {
Text(palette.name)
}
}
.navigationDestination(for: Palette.self) {
PaletteView(palette: palette)
}
.navigationTitle("\(store.name) Palettes")
}
}
}

struct PaletteView: View {
let palette: Palette

var body: some View {
...
}
.navgationTitle(palette.name)
}

Deleting/moving items in a ForEach with .onDelete/.onMove

Multithreading,Error Handing

Colors and Images

Color

Color是一个非常酷的结构体,因为它实现了很多不同的协议

Is a color-specifier .foregroundColor(Color.green)

Can also act like a ShapeStyle, .fill(Color.blue)填充颜色

Can also act like a View, Color.white

UIColor

存储颜色或表示颜色的方式

Color(uiColor:)

Image

Primarily serves as a View

Image(_ name: String)

Image(systemName:)

.imageScale()

UIImage

Is the type for actually creating/manipulating images and storing in vars.

Image(uiImage:)

Multithreaded Programming and ErrorHandling

Task

1
2
3
4
5
6
let task = Task(priority: TaskPriority? = nil) {

}

Task {
}

actor

actor是引用类型,位于堆中,能做到而结构体和类做不到的事情是同步对其变量和函数的所有反问。当我们尝试不让这些多线程代码片段破坏所有数据结构时,Actor就是同步的基本单位。

多线程安全访问如何实现?

  1. 任何时候都只能运行一个参与者的函数或变量
  2. 参与者中的函数要么运行完成(completion),要么被暂停(suspended)

使这一切正常运转的一个非常重要的部分,第一件事就是必须让系统知道什么时候一个功能可以暂停,我们通过将其标记为async来实现这一点,就像将throws放在可能抛出的函数上一样,我们将async放在可能被暂停的函数上。

  1. 当你调用async函数时,你必须使用关键字await,就像你调用一个函数时它会抛出throw,你必须说try

await意味着你正在等待那件可能需要很长时间的事情,因为它可以自行暂停。

async closures

1
2
.task(async () -> Void)
.refreshable(async () -> Void)

MainActor

所有UI活动必须在main actor内完成

Error Handing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func attendLecture() throws {
if sleptIn {
throw CS193pError.missedLecture
}
askQuestions() // not done if you sleptIn because throw exits attendLecture()
...
}

enum CS193pError: Error {
case lateHomework(daysLate: Int)
case missedLecture
var localizedDescription: String { switch self { .. } }
}

do {
try somethingThatThrow()
print("hello") // will only happen if somethingThatThrows() does not throw anything
} catch CS193pError.lateHomework(let daysLate) {

} catch let cs193pError where cs193pError is CS193pError{

} catch {

}
print("keep going")

Demo: Implementing our own version of AsyncImage

状态机编程: 状态机编程的思想是,当我执行某个过程时,我会考虑我可能处于的所有状态,将对它们进行实际编码,并以某种方式在代码中为每个步骤做标记,对于状态机来说,一个很棒的数据类型时枚举,因为根据定义,你正在穿越不同的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
enum Background {
case none
case fetching(URL)
case found(UIImage)
case failed(String)
}

enum FetchError: Error {
case badImageData
}

var uiImage: UIImage? {
switch self {
case .found(let uiImage): return uiImage
default: return nil
}
}

@Published private var emojiArt = EmojiArt() {
didSet {
...
if emojiArt.background != oldValue.background {
Task {
await fetchBackgroundImage()
}
}
}
}

@MainActor
private func fetchBackgroundImage() async {
if let url = emojiArt.background {
background = .fetching(url)
do {
background = .found(try await fetchUIImage(from: url))
} catch let error {
background = .failed("Couldn't set background: \(error.localizedDescription)")
}
} else {
background = .none
}
}

private func fetchUIImage(from url: URL) async throws -> UIImage {
let (data, _) = try await URLSession.shared.data(from: url)
if let uiImage = UIImage(data: data) {
return uiImage
} else {
throw FetchError.badImageData
}
}

问题?
当你从一个很慢的网站下载一张图片时,这会花很长时间,然后你想放弃,从一个网速快的网站上拖出一张图片,米秒钟会发生什么?很慢的网站图片会覆盖很快的图片,因为它们两个都被分叉出去做事,当长图出现在这里时,他返回那个UIImage时,你没有检查用户是否选择了其他东西,所以每次你调用等待(await)时,当它回来时,你的世界是否可能发生了改变?

1
2
3
4
5
6
7
do {
let image = try await fetchUIImage(from: url)
if url == emojiArt.backround {
background = .found(image)
}
}

Document Architecture

Components of a “document-oriented” application

App Protocol

App中的var body类型是some Scene,你拥有多个Scene,因为你的appiPadMac上有多个窗口,每个窗口都是一个Scene

Scene Protocol

Scene是一个容器,一个包含顶级视图的窗口,你可以创建自己的Scene,就像创建自己的视图一样,这比较罕见。

你可能想要执行此操作的一个例子是想要查看名为scenePhase@Environment,它告诉你这个场景已经成为foreground场景,或者现在它在后台。

@Environment(.scenePahse)

1
2
3
WindowGroup { return aTopLevelView}
DocumentGroup( newDocument: ) { config in ... return aTopLevelView }
DocumentGroup( viewwing: ) { config in ... return aTopLevelView }

DocumentGroup

1
2
3
4
5
6
7
struct EmojiArtApp: App {
var body: some Scene {
DocumentGroup(newDocument: { EmojiArtDocument()}) { config in
EmojiArtDocumentView(document: config.document)
}
}
}

your ViewModel must conform to ReferenceFileDocument
must implement Undo in your application

FileDocument

This protocol gets/puts the contents of a document from/to a file

1
2
3
4
5
6
7
init(configuration: ReadConfiguration) throws {
if let data = configuration.file.regularFileContents {
// initialize yourself from the Data in that file
} else {
throw CocoaError(.fileReadCorruptFile) // should never happen
}
}

Writiing your document out to a file

1
2
3
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
FileWrapper(regularFileWithContents: /* myself as a Data*/)
}

fileWrapper是因为iOSMac OS中的一些文件历来被存储为一个目录,其中包含一堆文件

ReferenceFileDocument

1
2
3
func snapshot(contentType: UTType) throws -> Snapshot {
return /* my Model converted(possibly) to some other type, like a Data probably */
}

Snapshot type is a “don’t care”, but usually it’s a Data
UTType is a Uniform Type Identifier

writting your document out to a file

1
2
3
4
5
6
func fileWrapper(
snapshot: Snapshot,
configuration: WriteConfiguration
) throws -> FileWrapper {
FileWrapper(regurationFileWithContents: /* snapshot converd to a Data*/)
}

UTType(Uniform Type Identifier)

UTType 结构有一些静态数据,.jpeg,.text,.pdf

1
2
3
4
5
6
extension UTType {
static let emojiart = UTType(exportedAs: "edu.stanford.cs193p.emojiart")
}

static var readableContentTypes: [UTType] { [UTType.emojiart] }
static var writeableContentTypes: [UTType] { [UTType.emojiart] }

Undo

if you use ReferenceFileDocument,you must implement Undo

This is how SwiftUI knows that you’ve changed the document so it can autosave it.

This is all done with something called an UndoManager

1
2
3
4
5
6
7
8
9
10
11
12
@Environment(\.undoManager) var undoManager

func registerUndo(withTarget: self,howToUndo: (target) -> Void)

func undoablyPerform(with undoManager: UndoManager?,doit: ()->Void) {
let oldModel = model // save the model so we can undo back to it
doit()
undoManager?.registerUndo(withTarget: self){ myself in
myself.model = oldModel
}
undoManager?.setActionName(operation)
}

Notifications

可以异步的向代码的其他部分发送通知

System statics

1
2
Notification.Name(String)
UserDefaults.didChangeNotificatioin

NotificationCenter

In a View, use the .onReceive view modifier to receive notificaions …

1
.onReceive(NotificaionCenter.default.publisher(for: name)) { notification in ...}

in a ViewModel

1
2
3
4
5
6
7
8
9
10
11
var observer:  NSObjectProtocol?  //a cookie to later "stop listening" with
observer = NotificationCenter.default.addObserver(
forName: Notification.Name
object: nil,
queue: .main
) { (notification: Notification) -> Void in
let info: Any? = notification.userInfo
// info is usually a dictionary of notification-specific information
}

NotificationCenter.default.removeObserver(observer) // to stop listening

Posting a Notification

1
2
3
4
5
NotificationCenter.default.post(
name: Notification.Name,
object: Any?,
userInfo: [any Hashable: Any]? = nil
)

Demo

Font.body,Font.caption,Font.headline所有这些设置,都会跟随系统设置里字体大小的改变而自动缩放,但是下面却没有改变

1
private let paletteEmojiSize: CGFloat = 40

想要跟随系统动态字体设置变化需要

1
@ScaleMetric var paletteEmojiSize: CGFloat = 40